using BepuUtilities;
using DemoContentLoader;
using SharpFont;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

namespace DemoContentBuilder
{
    public static class FontBuilder
    {
        private static Dictionary<CharacterPair, int> ComputeKerningTable(Face face, string characterSet)
        {
            var kerningTable = new Dictionary<CharacterPair, int>();
            for (int i = 0; i < characterSet.Length; ++i)
            {
                var glyphIndex = face.GetCharIndex(characterSet[i]);
                for (int j = 0; j < characterSet.Length; ++j)
                {
                    var kerning = face.GetKerning(glyphIndex, face.GetCharIndex(characterSet[i]), KerningMode.Default);
                    if (kerning.X != 0)
                    {
                        kerningTable.Add(new CharacterPair(characterSet[i], characterSet[j]), kerning.X.ToInt32());
                    }
                }
            }
            return kerningTable;
        }


        private const string characterSet = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890`-=[]\;',./~!@#$%^&*()_+{}|:""<>? ";

        // 低于128/2^(5-1)=8像素的文本不会很常见,因此为它生成更多MIPS的价值相当低。
        // 从技术上讲,通过以非常高的分辨率输出并将其放大,我们错过了低分辨率的提示。
        // 这可以通过在每个MIP分辨率下实际栅格化字形并重新进行距离计算来解决。
        // 但实际上,我们可能只会使用较大的字形。
        private const int FontSizeInPixels = 128;
        private const int MipLevels = 5;
        private const int AtlasWidth = 2048;

        class CharacterHeightComparer : IComparer<CharacterData>
        {
            public int Compare(CharacterData a, CharacterData b)
            {
                return a.SourceSpan.Y < b.SourceSpan.Y ? 1 : a.SourceSpan.Y > b.SourceSpan.Y ? -1 : 0;
            }
        }

        public unsafe static FontContent Build(Stream fontDataStream)
        {
            var faceBytes = new byte[fontDataStream.Length];
            fontDataStream.Read(faceBytes, 0, faceBytes.Length);
            using (var library = new Library())
            {
                using (var face = new Face(library, faceBytes, 0))
                {

                    // 收集字形边界信息。
                    face.SetPixelSizes(FontSizeInPixels, FontSizeInPixels);
                    var sortedCharacterSet = new char[characterSet.Length];
                    var sortedCharacterData = new CharacterData[characterSet.Length];

                    for (int i = 0; i < characterSet.Length; ++i)
                    {
                        sortedCharacterSet[i] = characterSet[i];
                        face.LoadGlyph(face.GetCharIndex(characterSet[i]), LoadFlags.Default, LoadTarget.Normal);
                        ref var characterData = ref sortedCharacterData[i];
                        characterData.SourceSpan.X = face.Glyph.Metrics.Width.ToInt32();
                        characterData.SourceSpan.Y = face.Glyph.Metrics.Height.ToInt32();

                        characterData.Bearing.X = face.Glyph.Metrics.HorizontalBearingX.ToInt32();
                        characterData.Bearing.Y = face.Glyph.Metrics.HorizontalBearingY.ToInt32();

                        characterData.Advance = face.Glyph.Metrics.HorizontalAdvance.ToInt32();
                    }

                    // 接下来,在地图集中为每个角色分配空间。
                    // 按高度对字符进行排序,然后从地图集的一侧扫描到放置字符的另一侧。
                    // 一旦到达另一边,颠倒方向并重复。继续操作,直到不再有字符。
                    Array.Sort(sortedCharacterData, sortedCharacterSet, new CharacterHeightComparer());

                    const int padding = 1 << MipLevels;
                    var characters = new Dictionary<char, CharacterData>();
                    var packer = new FontPacker(AtlasWidth, MipLevels, padding, characterSet.Length);
                    for (int i = 0; i < sortedCharacterSet.Length; ++i)
                    {
                        // Packer类还处理放置逻辑并在字符数据中设置SourceMinimum。
                        packer.Add(ref sortedCharacterData[i]);
                    }

                    // 现在每个字形都已经定位在工作表中,我们实际上可以将字形Alpha栅格化成一个位图原型图集。
                    // 我们首先按顺序构建光栅化的集合,因此不必担心底层库中的线程化问题。
                    var rasterizedAlphas = new Texture2DContent(AtlasWidth, packer.Height, 1, 1);
                    for (int i = 0; i < sortedCharacterSet.Length; ++i)
                    {
                        // 栅格化字形。
                        var character = sortedCharacterSet[i];
                        face.LoadGlyph(face.GetCharIndex(character), LoadFlags.Default, LoadTarget.Normal);
                        face.Glyph.RenderGlyph(RenderMode.Normal);

                        // 将Alpha复制到像素Alpha缓冲区中的适当位置。
                        int glyphWidth = face.Glyph.Bitmap.Width;
                        int glyphHeight = face.Glyph.Bitmap.Rows;
                        var glyphBuffer = (byte*)face.Glyph.Bitmap.Buffer;


                        Int2 location;
                        location.X = sortedCharacterData[i].SourceMinimum.X;
                        location.Y = sortedCharacterData[i].SourceMinimum.Y;
                        for (int glyphRow = 0; glyphRow < glyphHeight; ++glyphRow)
                        {
                            Unsafe.CopyBlockUnaligned(
                                ref rasterizedAlphas.Data[rasterizedAlphas.GetRowOffsetForMip0(glyphRow + location.Y) + location.X],
                                ref glyphBuffer[glyphRow * glyphWidth], (uint)glyphWidth);
                        }
                    }

                    // 为全单精度浮点版本的地图集预先分配内存。这将被用作暂存存储器(诚然,比所需的要多)
                    // 其将在计算MIPS之后被编码成最终的单字节表示。全精度工作台使MIPS更精确一些。
                    var preciseAtlas = new Texture2DContent(AtlasWidth, packer.Height, MipLevels, 4);
                    var atlas = new Texture2DContent(AtlasWidth, packer.Height, MipLevels, 1);
                    // 计算贴图集中每个字符覆盖的纹理元素的距离。
                    var atlasData = atlas.Pin();
                    var preciseData = (float*)preciseAtlas.Pin();
                    var alphaData = rasterizedAlphas.Pin();
                    // for(int i=0;i<sortedCharacterData.Length;++i)
                    Parallel.For(0, sortedCharacterData.Length, i =>
                    {
                        // 请注意,字符周围的填充也应填入其距离。这样,不太详细的MIPS可以从有用的数据中提取。
                        ref var charData = ref sortedCharacterData[i];

                        var min = new Int2(charData.SourceMinimum.X, charData.SourceMinimum.Y);
                        var max = new Int2(charData.SourceMinimum.X + charData.SourceSpan.X, charData.SourceMinimum.Y + charData.SourceSpan.Y);
                        var paddedMin = new Int2(min.X - padding, min.Y - padding);
                        var paddedMax = new Int2(max.X + padding, max.Y + padding);
                        // 将每个字符纹理元素初始化为最大距离。接下来的BFS只会减少距离,所以它必须从高处开始。
                        var maxDistance = Math.Max(AtlasWidth, packer.Height);
                        for (int rowIndex = paddedMin.Y; rowIndex < paddedMax.Y; ++rowIndex)
                        {
                            var rowOffset = preciseAtlas.GetRowOffsetForMip0(rowIndex);
                            var distancesRow = preciseData + rowOffset;
                            for (int columnIndex = paddedMin.X; columnIndex < paddedMax.X; ++columnIndex)
                            {
                                distancesRow[columnIndex] = maxDistance;
                            }
                        }


                        // 扫描阿尔法。将字形的边框纹理元素添加到点集。我们收集了非零阿尔法轮廓和"负空间"零阿尔法轮廓。
                        // 扫描距离时,非零的Alpha纹素将查找到零Alpha纹素的最短距离,而零Alpha纹素将查找最短的距离
                        // 到非零Alpha纹理元素的距离。
                        var glyphOutline = new List<Int2>((max.X - min.X) * (max.Y - min.Y));
                        int coverageThreshold = 127;
                        for (int rowIndex = min.Y; rowIndex < max.Y; ++rowIndex)
                        {
                            // Alpha和地图集具有相同的尺寸,因此共享行偏移是安全的。
                            Debug.Assert(padding > 0, "This assumes at least one padding; no boundary checking is performed on the alpha accesses.");
                            var rowOffset = preciseAtlas.GetRowOffsetForMip0(rowIndex);
                            var alphasRow0 = alphaData + rowOffset - preciseAtlas.Width;
                            var alphasRow1 = alphaData + rowOffset;
                            var alphasRow2 = alphaData + rowOffset + preciseAtlas.Width;
                            for (int columnIndex = min.X; columnIndex < max.X; ++columnIndex)
                            {
                                if (alphasRow1[columnIndex] >= coverageThreshold)
                                {
                                    // 此纹理元素被认为是覆盖的。
                                    // 仅当至少有一个相邻的未覆盖纹理元素时,才将其添加到点集。
                                    // 如果这个旁边没有未覆盖的纹理元素,那么它不可能在表面上。
                                    if (alphasRow0[columnIndex] < coverageThreshold ||
                                        alphasRow1[columnIndex - 1] < coverageThreshold ||
                                        alphasRow1[columnIndex + 1] < coverageThreshold ||
                                        alphasRow2[columnIndex] < coverageThreshold)
                                    {
                                        Int2 texelCoordinates;
                                        texelCoordinates.X = columnIndex;
                                        texelCoordinates.Y = rowIndex;
                                        glyphOutline.Add(texelCoordinates);
                                    }
                                }
                            }
                        }

                        // 对于字符区域中的每个纹理元素,扫描字形点集以查找最近的纹理元素。
                        // 缓存我们前进的最大距离,这样我们就可以最大限度地提高这个角色的精确度。
                        float largestDistanceMagnitude = 0;
                        for (int rowIndex = paddedMin.Y; rowIndex < paddedMax.Y; ++rowIndex)
                        {
                            var rowOffset = preciseAtlas.GetRowOffsetForMip0(rowIndex); // 相同的维度;可以共享。
                            var distancesRow = preciseData + rowOffset;
                            var alphasRow = alphaData + rowOffset;
                            for (int columnIndex = paddedMin.X; columnIndex < paddedMax.X; ++columnIndex)
                            {
                                // 这是一个未覆盖的纹理元素。查找字形轮廓。
                                float lowestDistance = float.MaxValue;
                                for (int pointIndex = 0; pointIndex < glyphOutline.Count; ++pointIndex)
                                {
                                    var point = glyphOutline[pointIndex];
                                    var offsetX = point.X - columnIndex;
                                    var offsetY = point.Y - rowIndex;
                                    var candidateDistance = (float)Math.Sqrt(offsetX * offsetX + offsetY * offsetY);
                                    if (candidateDistance < lowestDistance)
                                        lowestDistance = candidateDistance;
                                }
                                // 如果没有覆盖,请使用正距离。如果它被覆盖了,请使用负距离。
                                distancesRow[columnIndex] = alphasRow[columnIndex] < coverageThreshold ? lowestDistance : -lowestDistance;
                                if (lowestDistance > largestDistanceMagnitude)
                                    largestDistanceMagnitude = lowestDistance;
                            }
                        }

                        // 构建MIPS。我们已经在此核心上缓存了所有数据;256KiB L2可以轻松地保存128x128字形的处理上下文。
                        // (尽管过分担心内容构建器中的性能是相当愚蠢的。我们不会经常构建字体。)
                        // 请注意,我们在打包过程中对齐并填充了每个字形。对于给定的MIP(N)中的纹理元素,可以安全地对MIP(n-1)中的四个父纹理元素进行采样。
                        for (int mipLevel = 1; mipLevel < preciseAtlas.MipLevels; ++mipLevel)
                        {
                            var mipMin = new Int2(paddedMin.X >> mipLevel, paddedMin.Y >> mipLevel);
                            var mipMax = new Int2(paddedMax.X >> mipLevel, paddedMax.Y >> mipLevel);

                            // 是的,这些会做一些多余的计算,但不,这无关紧要。
                            var parentMipStart = preciseData + preciseAtlas.GetMipStartIndex(mipLevel - 1);
                            var parentMipRowPitch = preciseAtlas.GetRowPitch(mipLevel - 1);
                            var mipStart = preciseData + preciseAtlas.GetMipStartIndex(mipLevel);
                            var mipRowPitch = preciseAtlas.GetRowPitch(mipLevel);
                            for (int mipRowIndex = mipMin.Y; mipRowIndex < mipMax.Y; ++mipRowIndex)
                            {
                                var mipRow = mipStart + mipRowIndex * mipRowPitch;
                                var parentRowIndex = mipRowIndex << 1;
                                var parentMipRow0 = parentMipStart + parentRowIndex * parentMipRowPitch;
                                var parentMipRow1 = parentMipStart + (parentRowIndex + 1) * parentMipRowPitch;
                                for (int mipColumnIndex = mipMin.X; mipColumnIndex < mipMax.X; ++mipColumnIndex)
                                {
                                    var parentMipColumnIndex0 = mipColumnIndex << 1;
                                    var parentMipColumnIndex1 = parentMipColumnIndex0 + 1;
                                    mipRow[mipColumnIndex] = 0.25f * (
                                        parentMipRow0[parentMipColumnIndex0] + parentMipRow0[parentMipColumnIndex1] +
                                        parentMipRow1[parentMipColumnIndex0] + parentMipRow1[parentMipColumnIndex1]);
                                }

                            }
                        }

                        // 现在所有MIPS都已填满,接下来将数据烘焙到最终的单字节编码中。
                        // 使用最大绝对距离作为编码乘数以最大化精度。
                        charData.DistanceScale = largestDistanceMagnitude;
                        var encodingMultiplier = 1f / largestDistanceMagnitude;
                        for (int mipLevel = 0; mipLevel < atlas.MipLevels; ++mipLevel)
                        {
                            var mipMin = new Int2(paddedMin.X >> mipLevel, paddedMin.Y >> mipLevel);
                            var mipMax = new Int2(paddedMax.X >> mipLevel, paddedMax.Y >> mipLevel);

                            // 请注意有符号字节。我们正在构建的是R8_SNORM纹理,而不是UNRM。
                            var encodedStart = (sbyte*)atlasData + atlas.GetMipStartIndex(mipLevel);
                            var preciseStart = preciseData + preciseAtlas.GetMipStartIndex(mipLevel);
                            var rowPitch = atlas.GetRowPitch(mipLevel);
                            for (int rowIndex = mipMin.Y; rowIndex < mipMax.Y; ++rowIndex)
                            {
                                var preciseRow = preciseStart + rowIndex * rowPitch;
                                var encodedRow = encodedStart + rowIndex * rowPitch;
                                for (int columnIndex = mipMin.X; columnIndex < mipMax.X; ++columnIndex)
                                {
                                    encodedRow[columnIndex] = (sbyte)(127 * Math.Max(-1, Math.Min(1, encodingMultiplier * preciseRow[columnIndex])));
                                }

                            }
                        }
                    });

                    // const int savedMip=0;//MipLeveles-1;
                    // var bitmap=新位图(atlas.Width>>savedMip,atlas.Height>>savedMip);
                    // var bitmapData=bitmap.LockBits(new Rectangle(new Point(),new size(bitmap.Width,bitmap.Height),
                    // ImageLockMode.WriteOnly,PixelFormat.Format32bppRgb);

                    // var scan0=(byte*)bitmapData.Scan0;
                    // var sourceStart=atlasData+atlas.GetMipStartIndex(SavedMip);
                    // for(int rowIndex=0;rowIndex<bitmapData.Height;++rowIndex)
                    // {
                    // var row=(int*)(scan0+bitmapData.Stride*rowIndex);
                    // var sourceRow=sourceStart+atlas.GetRowOffsetFromMipStart(savedMip,rowIndex);
                    // for(int column nIndex=0;column nIndex<bitmapData.Width;++column nIndex)
                    // {
                    // row[column nIndex]=sourceRow[column nIndex]|(sourceRow[column nIndex]<<8)|(sourceRow[column nIndex]<<16)|(sourceRow[column nIndex]<<24);
                    // }
                    // }
                    // bitmap.UnlockBits(BitmapData);
                    // bitmap.Save($"{face.FamilyName}Test.bmp",ImageFormat.Bmp);

                    atlas.Unpin();
                    preciseAtlas.Unpin();
                    rasterizedAlphas.Unpin();


                    // 构建字距调整表。
                    var kerning = ComputeKerningTable(face, characterSet);

                    // 既然字符数据包含归一化因子,我们就可以将其添加到最终字典中。
                    for (int i = 0; i < sortedCharacterData.Length; ++i)
                    {
                        characters.Add(sortedCharacterSet[i], sortedCharacterData[i]);
                    }

                    return new FontContent(atlas, face.FamilyName, 1f / FontSizeInPixels, characters, kerning);
                }
            }
        }
    }
}
